﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace GeekShop
{
    public partial class ColorBar : UserControl
    {
        #region Fields
        private Image buffer;
        private Gradient gradient;
        private Graphics canvas;
        private bool dragging = false;
        private int maxValue = 16777215;
        private Size margin = new Size(6, 0);
        private KeyValuePair<int, Marker> selection;
        #endregion
        
        #region Properties
        public Gradient Gradient
        {
            get { return this.gradient; }
            set { this.gradient = value; }
        }
        public KeyValuePair<int, Marker> Selection
        {
            get { return this.selection; }
            set
            {
                MarkerEventArgs m = new MarkerEventArgs();
                m.OldIndex = this.selection.Key;
                m.OldMarker = this.selection.Value;
                this.selection = value;
                m.NewIndex = this.selection.Key;
                m.NewMarker = this.selection.Value;
                this.SelectionChanged(this, m);
            }
        }
        #endregion

        #region Virtual Properties
        public new Size Size
        {
            get
            {
                return new Size(base.Width - 2 * this.margin.Width, base.Height - 2 * this.margin.Height);
            }
            set
            {
                base.Size = value;
                this.canvas = this.CreateGraphics();
                this.buffer = new Bitmap(value.Width, value.Height);
            }
        }
        public new int Width
        {
            get { return this.Size.Width; }
            set { this.Size = new Size(value, base.Height); }
        }
        public new int Height
        {
            get { return this.Size.Height; }
            set { this.Size = new Size(base.Width, value); }
        }
        public new int Left { get { return this.margin.Width; } }
        public new int Right { get { return this.Left + this.Size.Width; } }
        public new int Top { get { return this.margin.Height; } }
        public new int Bottom { get { return this.Top + this.Size.Height; } }
        private SortedDictionary<int, Marker> Markers
        {
            get { return this.gradient.Markers; }
            set { this.gradient.Markers = value; }
        }
        #endregion

        #region Constructors
        public ColorBar(Gradient gradient)
        {
            this.gradient = gradient;
            InitializeComponent();
        }
        public ColorBar() : this(Gradient.Empty) { }
        #endregion

        #region Private Methods
        private bool tryAddMarker(int index)
        {
            if (!this.Markers.ContainsKey(index))
            {
                Marker marker = new Marker(Color.DarkRed);
                this.Markers.Add(index, marker);
                this.Selection = new KeyValuePair<int, Marker>(index, marker);
                return true;
            }
            return false;
        }
        private KeyValuePair<int, Marker> NearestMarker(int index)
        {
            int minDistance = int.MaxValue, nearestIndex = 0;
            SortedDictionary<int, Marker>.Enumerator enumerator = this.Markers.GetEnumerator();
            while (enumerator.MoveNext())
            {
                int currentDistance = Math.Abs(enumerator.Current.Key - index);
                if (currentDistance < minDistance)
                {
                    nearestIndex = enumerator.Current.Key;
                    minDistance = currentDistance;
                }
            }
            Marker marker;
            this.Markers.TryGetValue(nearestIndex, out marker);

            return new KeyValuePair<int, Marker>(nearestIndex, marker);
        }
        private int getIndex(int position)
        {
            return (int)((double)(position - this.Left) / this.Width * this.maxValue);
        }
        private void move(int position)
        {
            if (this.dragging &&
                position > this.Left &&
                position < this.Right &&
                this.selection.Key > 0 &&
                this.selection.Key < this.maxValue)
            {
                int newIndex = this.getIndex(position);
                this.Markers.Remove(this.selection.Key);
                try
                {
                    this.Markers.Add(newIndex, this.selection.Value);
                }
                catch (ArgumentException)
                {
                    int step = this.getIndex(1);

                    while (this.Markers.ContainsKey(newIndex))
                    {
                        if (newIndex >= this.maxValue)
                        {
                            step *= -1;
                            newIndex = this.selection.Key;
                        }
                        newIndex += step;
                    }
                    this.Markers.Add(newIndex, this.selection.Value);
                }
                finally
                {
                    this.selection = new KeyValuePair<int, Marker>(newIndex, this.selection.Value);
                }
            }
        }
        private new void Paint()
        {
            #region Preparations
            Graphics g = Graphics.FromImage(this.buffer);
            #endregion
            #region Draw Background
            g.FillRectangle(Picture.BackgroundBrush, this.Left, this.Top, this.Width, this.Height);
            #endregion
            #region Draw Border
            g.FillRectangle(new SolidBrush(this.BackColor), 0, 0, base.Width, 11);
            #endregion
            #region Draw Bar
            for (int x = this.Left; x < this.Right; x++)
            {
                int index = this.getIndex(x);
                g.DrawLine(new Pen(this.gradient[index]), x, this.Top + 11, x, this.Bottom);
            }
            #endregion
            #region Draw Marker
            SortedDictionary<int, Marker>.Enumerator enumerator = this.Markers.GetEnumerator();
            while (enumerator.MoveNext())
            {
                int x = this.Left + (int)Math.Round((double)enumerator.Current.Key / this.maxValue * this.Width);
                g.DrawImage(enumerator.Current.Value.Bitmap, x - 5, 0, 11, 16);
            }
            #endregion
            #region Draw Buffer to Canvas
            this.canvas.DrawImage(this.buffer, 0, 0);
            #endregion
        }
        #endregion

        #region Public Methods
        public bool tryRemoveMarker(MarkerEventArgs m)
        {
            if (m.OldIndex == 0) return false;
            if (m.OldIndex == this.maxValue) return false;
            if (this.Markers.Count < 3) return false;
            if (!this.Markers.ContainsKey(m.OldIndex)) return false;

            this.Markers.Remove(m.OldIndex);
            this.Paint();
            return true;
        }
        public void MoveMarker(MarkerEventArgs m)
        {
            this.Markers.Remove(m.OldIndex);
            this.Markers.Add(m.NewIndex, m.NewMarker);
            this.Paint();
        }
        public bool trySetColor(MarkerEventArgs m)
        {
            try
            {
                this.Selection.Value.Color = m.NewMarker.Color;
            }
            catch (Exception)
            {
                return false;
            }
            this.Paint();
            return true;
        }
        #endregion

        #region Overwritten Methods
        protected override void OnPaint(PaintEventArgs e)
        {
            // Garbage Collection dumps the Graphics when Hide()ing the form
            this.canvas = this.CreateGraphics();
            this.Paint();
        }
        #endregion

        #region Events
        public event MarkerSelectionChangedHandler SelectionChanged;
        #endregion

        #region Event Methods
        private void ColorBar_MouseDown(object sender, MouseEventArgs e)
        {
            int index = this.getIndex(e.Location.X);
                
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                try
                {
                    this.Selection = this.NearestMarker(index);
                    this.dragging = true;
                }
                catch (ArgumentOutOfRangeException)
                {
                    this.dragging = false;
                }
            }
            else if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                this.tryAddMarker(index);
                this.Paint();
            }
        }
        private void ColorBar_MouseMove(object sender, MouseEventArgs e)
        {
            if (this.dragging && e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                this.move(e.Location.X);
                this.Paint();
                MarkerEventArgs m = new MarkerEventArgs();
                m.NewIndex = this.selection.Key;
                m.NewMarker = this.selection.Value;
                this.SelectionChanged(this, m);
            }
        }
        #endregion
    }
}
